热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

都会|也就是_Linux进程理解

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Linux进程理解相关的知识,希望对你有一定的参考价值。目录一、冯诺依曼

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Linux进程理解相关的知识,希望对你有一定的参考价值。



目录


  • 一、冯诺依曼体系
  • 二、操作系统概念
  • 三、进程
    • 3.1 进程概念
    • 3.2 描述进程-PCB
    • 3.3 进程状态

  • 四、进程地址空间


一、冯诺依曼体系

我们先来看一下日常生活中,我们所用的电脑或者公司用的服务器都遵守冯诺依曼体系

由五种组成,分别是输入设备,存储器,运算器,控制器和输出设备



输入单元:鼠标,键盘,扫描仪,写板,网卡,磁盘,话筒等
存储器:也就是物理内存,后续讲进程地址会详细讲
运算器和控制器:都属于CPU,运算器在CPU中会做两件事一是执行算术,二是执行逻辑
输出单元:显示器,网卡,磁盘,音响


输入和输出设备统称为外设,其次CPU并不和外设打交道,同样的外设只和内存打交到

过程:输入单元输入的是数据,数据写入存储器进行处理(运算器和控制器)然后再传出输出设备,因此冯诺依曼规定了硬件层面上的数据流向

那么从冯诺依曼体系的角度来解释一道题:
小明在广东给远在安徽的小红发了一句在吗?那么请问发送了这个“在吗?”信息经过了哪些步骤?
ans:输入在吗?通过输入设备,然后存储到存储器,qq软件运行的时候也跑在存储器上,通过加密运算等过程再写回到存储器然后qq再把数据刷新出去,到输出设备,此时的输出设备叫网卡。通过网络朋友家接收数据的输入设备此时是网卡,不是键盘然后网卡的数据首先也会贯道内存里,qq在内存中获得消息,经过CPU运算解包再写到存储器里,然后存储器再定时的刷到显示器上,朋友最终收到了消息


二、操作系统概念

操作系统是进行软硬件资源管理的软件,管理硬件用struct结构体进行描述,用链表或其他高效数据结构进行组织,操作系统包括以下:


  1. 内核(进程管理,内存管理,文件管理,驱动管理)
  2. 其他程序(例如函数库,shell程序等)

为什么会有有操作系统?
3. 可以减少用户使用计算机的成本
4. 对下管理好所有的软硬件,对上对用户提供一个稳定高效的运行环境


三、进程

3.1 进程概念

那么什么是进程?
进程 = 你的程序 + 内核申请的数据结构(PCB),程序加载到内存里,操作系统为此创建PCB结构体变量然后里面填上该进程的属性信息最终完成进程创建

举个例子,当我们启动一个google浏览器,或者进行下载等,程序一旦运行就是进程。
进程是资源分配的最小单位,且每个进程拥有独立的地址空间

那么问题来了!!
如果我们开启了几十个进程呢?对于操作系统来说就必须要进行管理
正如上面所提到的我们对于每个进程都遵守先描述再组织的原则,先描述就是为每个进程创建一个进程控制块PCB,进程信息被放在进程控制块的数据结构中,然后一个个的进程由链表或其他高效数据结构进行组织


3.2 描述进程-PCB

那么我们就详细了解一下PCB,可以理解为进程属性的集合,就好像在自然界中我们看到一个动物会通过它的特征进行描述组织从而精准的判断。
在Linux操作系统下的描述进程结构体叫做task_struct
task_struct是linux内核中的一种数据结构,被装在到内存里并且包含着对应进程的信息



  1. 标示符: 描述本进程的唯一标示符,用来区别其他进程。也就是我们在Linux下看到的PID
    我们可以grep一个进程看到
  2. 状态: 任务状态,退出代码,退出信号等。
  3. 优先级: 相对于其他进程的优先级。
  4. 程序计数器: 程序中即将被执行的下一条指令的地址。
  5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  6. 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  7. I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  8. 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等

注意:进程放在CPU上后,不是一直在运行知道进程结束的

一般进程让出CPU有两种情况


  1. 来了一个优先级更高的进程
  2. 时间片到了

并发和并行的概念:
单CPU:跑起来多个进程,通过进程快速切换的方式,有个时间片,在一段时间内给进程运行一段固定的时间片时间,然后给下一个,从而实现并发
多核CPU:任何时刻,允许多个进程同时执行,并行

进程间切换:
最重要的就是保存上下文,把临时数据保存在PCB中,让出给其他的进程
如果有4个进程被切,是出于运行状态的,所以我们的操作系统会给每个CPU形成对应的 运行队列,通过运行队列把所有的PCB链接起来(也是用全局的链表连接起来,因为PCB里面包含了大量的指针,和文件等其他事务关联),这些队列是CPU所绑定的运行队列,CPU拿任务时就从这个队列中去拿任务,状态都为 R 状态

操作系统进行进程调度,CPU执行
总结:


  1. 我们的进程进行切换时,因为某些原因如时间片到了,或者遇到了一个优先级更高的进程,当前正在运行的进程需要保存上下文信息,然后让出CPU,另一个新到来的进程需要将自己的数据放入寄存器中,当被切走的进程回来时,也需要首先做恢复工作,这就叫上下文的保存与恢复
  2. 每一个进程都隶属于某一个运行队列,CPU直接从队列中调度

代码中用fork进行理解


  1. 程序员角度:父子共享用户代码,而用户数据各自私有一份。其中父子共享用户代码是只读的,不可修改和写入。 为什么要私有?操作系统中,所有进程具有独立性,如一个app挂掉而不影响另外一个进程的运行。 所以私有一份,不让进程相互干扰所以是只读的不能修改
  2. 内核角度:fork后,系统多了一个进程。创建子进程,通常以父进程为模版其中子进程默认使用的是父进程的代码和数据(写时拷贝)

为什么给子进程返回的是0,父进程返回子进程的pid?
儿子 -> 唯一父亲 , 因此儿子找父亲是特别简单的,是0
父亲 -> 多个子女 ,而一个父亲找特定的儿子是特别难的,所以要用不同ID区分


3.3 进程状态


通过task state array定义可以看出一共有七种状态


  1. R状态可以时正在运行,但是R状态有时候也并不代表是在CPU上面运行,进程在队列中等待被调度时,或者说进程准备就绪时也是R状态
  2. S状态,也叫浅度休眠,对外部事件可以做出反应,如ctrl+c 可以停止休眠
  3. D状态,也叫深度休眠,不可以被kill掉,即便是操作系统通常在访问磁盘进行数据拷贝的关键步骤上是需要将进程设置为Deep sleep的,只能等待D状态进程自动醒来,或是关机重启
  4. 小T状态,是一种等待状态





-18让进程又开始跑起来



  1. 大T状态,tracing stop用于打断点,进程就不跑了

  2. Z状态,僵尸状态
    进程退出时会将自己退出的相关信息写入进程的PCB中,供OS或者父进程来进行读取,其中我们把一个进程退出了但还没有被读取的时间点,我们称该进程来僵尸状态。
    注意:这里Z状态一直不退出,PCB要一直维护。如果父进程创建了很多子进程就是不会收,就造成了内存资源浪费,因为数据结构对象本身就要占用内存,换言之造成内存泄漏

  3. X状态,进程结束
    读取成功后,该进程才算真正死亡!为X状态

举个僵尸状态的例子:


当子进程执行完自己的事情后退出了,父进程还在S状态,但父进程不回收子进程,这时候子进程都会处于僵尸状态

僵尸进程本质上是为了维护一种临时状态,让我们父进程对子进程进行读取,进而回收资源,以及释放对应进程所占的资源

⚠️注意:Z状态已经挂掉,是不能被Kill掉的,plus 深度休眠状态也是不能kill掉的


  1. 孤儿进程
    先来看一组代码
    下面的代码父进程首先fork后,父进程先退出了,那么就不会去读取子进程的返回代码了
    那么这个老爹退出了之后,子进程还在S状态,需要被领养,也就是被PPID 为1操作系统给领养
    那么这个被领养的进程就叫孤儿进程,非常的形象生动吧!

    我们给下命令> top
    查看 一下1号进程

四、进程地址空间

前面的都是开胃菜,这里才是重头戏!
首先看一幅图
进程地址空间从下到上为低地址到高地址

printf("Lowest area: code Address : %p \\n", main); //main是函数,一定是属于代码的一部分,所以可以充当代码段
const char* p = "I'm in const or you can call me static area"; //栈区定义一个p变量,p变量保存的是常量区的字符串
printf("Read Only : %p \\n", p); //p就是保存该常量的起始地址, 如果是static int,也在这里(生命周期全局)
printf("global initAlready : %p \\n", &g_InitAlready);
printf("global Uninitalized : %p \\n", &g_unInit);
char *q = (char*)malloc(10);
printf("Heap Addr : %p \\n", q); //注意是q,而不是&q,如果是&q则是保持在栈区的变量名地址,而不是q内容
printf("Stack Addr p : %p \\n", &p); //栈区向下增长,p先入栈,因此地址会比q的要大
printf("Stack Addr q : %p \\n", &q);

printf("args Addr : %p \\n", argv[0]);
printf("argv Addr : %p \\n", argv[argc-1]); //带一些选项就不一样了
printf("Env Addr : %p \\n", env[0]);


上图论证了各个区域所存放的地址,从代码段到环境变量区域由低向高走

进程地址空间,不是内存!是虚拟出来的,后面会讲到到每一个进程都会认为自己有一块这么大的空间。
进程地址空间,会在进程的整个生命周期一直存在,直到进程退出! 这就是为什么定义的全局变量在为初始化,初始化,静态区一直存在了



我们可以论证这样一个观念:
假设内存是4GB,创建出来的每个进程都会认为自己有一块这么大的空间




val = 0的情况
如果一个进程创建了一个child后sleep 3秒钟,child先跑,对val进行修改后为10,父亲再读取这个val,child打印出来的val=10,而父val=0,但是发现他们的地址尽然一样!,这也从侧面论证了我们看到的不是实实在在的物理地址,而进程地址空间本质上就是一种虚拟地址。他们的值可以不一样是因为进程间是相互独立的,代码虽然是共享,数据是各自私有的,当发生写的动作时就是写时拷贝


进程地址空间的数据结构:

地址空间是在进程和物理空间之间的一个软件层,通过mm_struct来模拟 让操作系统给每个进程画大饼,让每个进程都认为自己有整个地址,或者说物理内存,这样我们的每个进程就可以根据自己的地址空间来划分自己的代码



地址空间和物理内存之间的关系:


  1. 把进程的代码和数据加载到内存中,并给进程创建进程地址空间
  2. 给每个进程创建一个页表结构,页表构建的就是从进程地址空间出来的虚拟地址到物理地址当中的映射

因此我们就可以说父子进程打印出来的地址可以是完全一样,而值不同

页表有权利去控制某段数据有没有权限写进物理内存,也就是说,地址空间中的字符常量区和代码区给到页表是只读权限的,我们创建的一个指针变量p,在栈中保存,指向的数据在常量区,不能进行修改,报segmentaltion fault

那么问题来了:



为什么不把输入设备的值直接放入物理地址不就完事了吗?还每个进程额外虚拟出来一块地址空间干嘛呢?
如果直接访问物理内存,进程一多就有可能发生指针越界,那么进程的独立性就无法得到保证,因为物理内存暴露,可能有恶意程序直接对内存数据进程篡改或者读取了


有了地址空间的好处:



  1. 保护物理内存,不收到任何进程内的地址的直接访问,方便进行合法性检测
  2. 将内存管理和进程管理进行解构( 比如创建进程,只需在页表中向系统申请内存,当进程释放只需通过页表释放内存(内存管理只需知道哪些内存区域(Page页表)是无效的,哪些是有效的),但如果没有页表进程和物理内存之间是强耦合的
  3. 让每个进程,以同样的方式,来看待代码和数据。想象一下当每个可执行程序都已经在执行前被划分好了一块块区域,通过页表,物理地址可以进行高效访问



创作不易,如果文章对你帮助的话,点赞三连哦:)


推荐阅读
  • 本文探讨了容器技术在安全方面面临的挑战,并提出了相应的解决方案。多租户保护、用户访问控制、中毒的镜像、验证和加密、容器守护以及容器监控都是容器技术中需要关注的安全问题。通过在虚拟机中运行容器、限制特权升级、使用受信任的镜像库、进行验证和加密、限制容器守护进程的访问以及监控容器栈,可以提高容器技术的安全性。未来,随着容器技术的发展,还需解决诸如硬件支持、软件定义基础设施集成等挑战。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • svnWebUI:一款现代化的svn服务端管理软件
    svnWebUI是一款图形化管理服务端Subversion的配置工具,适用于非程序员使用。它解决了svn用户和权限配置繁琐且不便的问题,提供了现代化的web界面,让svn服务端管理变得轻松。演示地址:http://svn.nginxwebui.cn:6060。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 本文介绍了使用readlink命令获取文件的完整路径的简单方法,并提供了一个示例命令来打印文件的完整路径。共有28种解决方案可供选择。 ... [详细]
author-avatar
无心少年丶的诱惑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有